package it.bsoft.gameboy.cpu;

import it.bsoft.bin.util.UByte;
import it.bsoft.bin.util.UShort;
import it.bsoft.gameboy.memory.ByteRegister;
import it.bsoft.gameboy.memory.Memory;
import it.bsoft.gameboy.memory.ShortRegister;
import it.bsoft.gameboy.memory.ShortSplittableRegister;

/**
 * This class is the gameboy version of the z80 cpu
 * @author GMANNA
 *
 */
public class Z80Cpu {
	
	/**
	 * This is the default value for the stack pointer
	 */
	public final static int START_OF_STACK = 0xFFFE;
	
	/**
	 * This is the default value for the program counter
	 */
	public final static int START_OF_PROGRAM = 0x100;
	
	/**
	 * 16 Kbytes of RAM
	 */
	private final static int MEMORY_SIZE = 1024*16;
	/* 16-Bit splittable registers */
	
	/* A-F */
	private final ShortSplittableRegister AFRegister = new ShortSplittableRegister();
	
	/* B-C */
	private final ShortSplittableRegister BCRegister = new ShortSplittableRegister();
	
	/* D-E */
	private final ShortSplittableRegister DERegister = new ShortSplittableRegister();
	
	/* H-L */
	private final ShortSplittableRegister HLRegister = new ShortSplittableRegister();
	
	/* 8-Bit splitted registry , (just a reference to the high/low registry) */
	
	private final ByteRegister ARegister = this.AFRegister.getHighRegister(); // A
	private final ByteRegister FRegister = this.AFRegister.getLowRegister(); // F
	
	private final ByteRegister BRegister = this.BCRegister.getHighRegister(); // B
	private final ByteRegister CRegister = this.BCRegister.getLowRegister(); // C
	
	private final ByteRegister DRegister = this.DERegister.getHighRegister(); // D
	private final ByteRegister ERegister = this.DERegister.getLowRegister(); // E
	
	private final ByteRegister HRegister = this.HLRegister.getHighRegister(); // H
	private final ByteRegister LRegister = this.HLRegister.getLowRegister(); // L
	
	/* 16-Bit non splittable registers */
	
	//Stack Pointer
	private final ShortRegister stackPointer = new ShortRegister().setValue(START_OF_STACK);
	
	//Program Counter
	private final ShortRegister programCounter = new ShortRegister().setValue(START_OF_PROGRAM);
	

	private Memory ram = new Memory(MEMORY_SIZE);
	
	/**
	 * Store the value of the given register in the 8-bit memory address of the instruction
	 * @param instruction
	 * @param reg
	 */
	protected void loadByteRegToRam(ByteRegister reg){
		UByte payload = this.ram.readByteAt(this.programCounter.getValue().intValue()+1);
		this.ram.writeByteAt(reg.getValue(), payload.shortValue());
		next(2);
	}
	
	/**
	 * store the value of the register src into the memory address dest
	 * @param dest
	 * @param src
	 */
	protected void loadByteRegToRamIndirect(ShortSplittableRegister dest,ByteRegister src){
		this.ram.writeByteAt(src.getValue(),dest.getValue().intValue());
		next();
	}
	
	/**
	 * store the value src into the memory address dest
	 * @param dest
	 * @param src
	 */
	protected void loadByteRegToRamImmediate(ShortSplittableRegister dest,UByte src){
		this.ram.writeByteAt(src,dest.getValue().intValue());
		next();
	}
	
	/**
	 * Store the content of the 8-bit input register
	 * into memory at the address written in the 16-bit dest register
	 * @param src
	 * @param dest
	 */
	protected void loadByteRamToRegIndirect(ByteRegister dest,ShortSplittableRegister src){
		dest.setValue(this.ram.readByteAt(src.getValue().intValue()));
		next();
		
	}
	
	
	/**
	 * Copy the value from the src 8-bit reg to the dest 8-bit reg
	 * @param src
	 * @param dest
	 */
	protected void loadByteRegToReg(ByteRegister dest,ByteRegister src){
		dest.setValue(src.getValue());
		next();
	}
	
	
	/**
	 * Returns the memory 8-bit value at the 16-bit address written in
	 * the given register
	 * @param reg
	 * @return
	 */
	private UByte resolveIndirectByte(ShortSplittableRegister reg){
		return this.ram.readByteAt(reg.getValue().intValue());
	}
	
	/**
	 * Returns the memory 16-bit value at the 16-bit address written in
	 * the given register
	 * @param reg
	 * @return
	 */
	private UShort resolveIndirectShort(ShortSplittableRegister reg){
		return this.ram.readShortAt(reg.getValue().intValue());
	}
	
	/**
	 * fetch an instruction from the memory and execute it
	 */
	protected void fetchAndExecuteInstruction(){
		UByte opCode = this.ram.readByteAt(this.getProgramCounter().getValue().intValue());
		this.executeInstruction(opCode);
	}
	
	/**
	 * Increments the program counter
	 * @param amount
	 */
	private void next(int amount){
		UShort current = this.programCounter.getValue();
		this.programCounter.setValue(current.sum(UShort.fromLong(amount)));
	}
	
	/**
	 * Increments the program counter by 1
	 */
	private void next(){
		next(1);
	}
	
	
	/**
	 * Executes the given instruction
	 * @param instruction
	 */
	private void executeInstruction(UByte opcode){
		OpCode.decode(opcode.shortValue()).execute(this);
	}
	
	
	
	
	
	
	
	
	
	
	
	
	/* getters and setters */
	
	public Memory getRam(){
		return this.ram;
	}

	public ShortSplittableRegister getAFRegister() {
		return AFRegister;
	}

	public ShortSplittableRegister getBCRegister() {
		return BCRegister;
	}

	public ShortSplittableRegister getDERegister() {
		return DERegister;
	}

	public ShortSplittableRegister getHLRegister() {
		return HLRegister;
	}

	public ByteRegister getARegister() {
		return ARegister;
	}

	public ByteRegister getFRegister() {
		return FRegister;
	}

	public ByteRegister getBRegister() {
		return BRegister;
	}

	public ByteRegister getCRegister() {
		return CRegister;
	}

	public ByteRegister getDRegister() {
		return DRegister;
	}

	public ByteRegister getERegister() {
		return ERegister;
	}

	public ByteRegister getHRegister() {
		return HRegister;
	}

	public ByteRegister getLRegister() {
		return LRegister;
	}
	
	public ShortRegister getProgramCounter() {
		return programCounter;
	}
	
	protected static interface OpCodeCommand{
		
		public void execute(Z80Cpu cpu);
	}
	
}
